/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author treiman
 */

/*
 * java.io.* for File.
 */
import java.io.*;
/*
 * java.net.* For Socket.
 */
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.net.InetSocketAddress;
import java.io.ObjectOutputStream;

public class VectorClockClient implements Runnable {
    /*
     * Attributes
     */

    /*
     * The client number is store to provide fast
     * array access when, for example, a thread's own
     * clock simply needs to be incremented.
     */
    private int clientNumber;
    private File configFile, inputFile;
    int[] vectorClock;

    /*
     * Constructor
     * @param
     * - File config
     * - int line
     * - File input
     * - int clients
     */
    public VectorClockClient(File config, int line, File input, int clients) {
        /*
         * Make sure that File handles aren't null and that
         * the line number is valid.
         */
        if (config != null && line >= 0 && input != null) {
            configFile = config;
            inputFile = input;
            clientNumber = line;
            /*
             * Set the array size to the number of lines found in the
             * config file and initialize with zero values.
             */
            vectorClock = new int[clients];
            for (int i = 0; i < vectorClock.length; i++) {
                vectorClock[i] = 0;
            }
        }
    }
    
    /*
     * Method for parsing the port to use for this client.
     * Counts the lines in the config file until the 
     * client number of this clients and selects the port 
     * from that line.
     */
    private int parsePort() {
        /*
         * The int value to be returned.
         */
        int returnable = 0;
        try {
            FileInputStream fstream = new FileInputStream(configFile.getName());
            DataInputStream in = new DataInputStream(fstream);
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            String strLine = "";
            /*
             * Iterate through the file until the desired line 
             * is reached.
             */
            for (int i = 0; i < clientNumber + 1; i++) {
                strLine = br.readLine();
            }
            /*
             * Split the line using space as the delimiter.
             */
            String[] tokens = strLine.split(" ");
            /*
             * Parse the port number to int and 
             * set it to be returned.
             */
            returnable = Integer.parseInt(tokens[1]);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        return returnable;
    }
    
    /*
     * Method for incrementing the logical clock 
     * of this client by one.
     */
    private void increment() {
        this.vectorClock[this.clientNumber]++;
    }
    
    /*
     * Overloaded method for incrementing the 
     * logical clock of this client by the amount given 
     * as parameter.
     */
    private void increment(int i) {
        /*
         * Check that the given value is valid.
         */
        if (i >= 0) {
            this.vectorClock[this.clientNumber] += i;   
        }
    }
    
    /*
     * Method for iterating through the received timestamp 
     * vector and setting each VC[k] to the max of VC[k] and that of 
     * the received vector.
     */
    private void synchronize(int[] clock) {
        /*
         * Check that the vectors are of identical length.
         */
        if (clock.length == this.vectorClock.length) {
            for (int i = 0; i < clock.length; i++) {
                if (clock[i] > vectorClock[i]) {
                    vectorClock[i] = clock[i];
                }
            }
        }
        else {
            System.out.println("Error: Missing array values");
        }
    }
    
    /*
     * Overloaded method for parsing the port used when sending messages 
     * to other clients.
     * @param
     * - int client: The number of the client. This is used to 
     *   determine the row from which the target port number is to 
     *   ber parsed.
     */
    private int parsePort(int client) {
        int returnable = 0;
        try {
            FileInputStream fstream = new FileInputStream(configFile.getName());
            DataInputStream in = new DataInputStream(fstream);
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            String strLine = "";
            for (int i = 0; i < client; i++) {
                strLine = br.readLine();
            }
            String[] tokens = strLine.split(" ");
            returnable = Integer.parseInt(tokens[1]);
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        return returnable;
    }
    
    private String parseHost() {
        String host = null;
        try {
            FileInputStream fstream = new FileInputStream(configFile.getName());
            DataInputStream in = new DataInputStream(fstream);
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            String strLine = "";
            for (int i = 0; i < clientNumber; i++) {
                strLine = br.readLine();
            }
            String[] tokens = strLine.split(" ");
            host = tokens[0];
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        return host;
    }
    
    private String parseHost(int client) {
        String host = null;
        try {
            FileInputStream fstream = new FileInputStream(configFile.getName());
            DataInputStream in = new DataInputStream(fstream);
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            String strLine = "";
            for (int i = 0; i < client; i++) {
                strLine = br.readLine();
            }
            String[] tokens = strLine.split(" ");
            host = tokens[0];
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        return host;
    }
    
    /*
     * Method for parsing the action from a line taken from 
     * the input file.
     * @param
     * - String s: a line from the input file.
     */
    private int parseAction(String s) {
        /*
         * Set the returnable value to -1 by default.
         */
        int returnable = -1;
        try {
            FileInputStream fstream = new FileInputStream(configFile.getName());
            DataInputStream in = new DataInputStream(fstream);
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            /*
             * Split the line using space as the delimiter.
             */
            String[] tokens = s.split(" ");
            /*
             * Store the left hand side to int lhs.
             */
            int lhs = Integer.parseInt(tokens[0]);
            /*
             * Store the right hand side to int rhs.
             */
            int rhs = Integer.parseInt(tokens[2]);
            /*
             * Store the action indicator to String action.
             */
            String action = tokens[1];
            /*
             * If the left hand side involves this client..
             */
            if (lhs == (this.clientNumber + 1)) {
                /*
                 * ..and if the actions is set to "send"
                 */
                if (action.equals("M")) {
                    /*
                     * ..then return the client number of the 
                     * receiving client.
                     */
                    returnable = rhs;
                }
                /*
                 * Otherwise, if the action is set to "increment clock", 
                 * increment the clock by the value indicated in int rhs. 
                 */
                else if (action.equals("L")) {
                    this.increment(rhs);
                    returnable = -1;
                }
            }
            /*
             * If the client is not mentioned in the left hand side,
             * check if the action is "send" and if this client is marked as 
             * the receiver. 
             */
            else if (action.equals("M") && (rhs == this.clientNumber + 1)) {
                returnable = 0;
            }
            /*
             * In all other cases, return -1.
             */
            else {
                returnable = -1;
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        return returnable;
    }

    /*
     * Do the actual work.
     */
    public void run() {
        try {
            /*
             * Synchronize the starting point of all threads.
             */
            InitClients.barrier.await();
        }
        catch (Exception e) {
            System.out.println(e);
        }
        /*
         * Get the port number for this client.
         */
        int port = parsePort();
        /*
         * Get the host name for this port
         * SET MANUALLY AT THE MOMENT, TO DO
         */
        //String hostname = "localhost";
        String hostname = this.parseHost();
        /*
         * String variable for saving the read lines.
         */
        String strLine;
        /*
         * ServerSocketChannel to be used.
         */
        ServerSocketChannel ssc;
        /*
         * SocketChannel to be used.
         */
        SocketChannel sc;
        /*
         * Equipment for reading from files.
         */
        FileInputStream fstream;
        DataInputStream in;
        BufferedReader br;
        /*
         * The END-OF-FILE flag, false by default.
         */
        boolean eof = false;
        try {
            /*
             * Open the Server-side socket and bind to the 
             * retrieved hostname and port number.
             */
            ssc = ServerSocketChannel.open();
            ssc.socket().bind(new InetSocketAddress(hostname, port));
            /*
             * Set server I/O to blocking.
             */
            ssc.configureBlocking(true);
            /*
             * Open the input stream.
             */
            //fstream = new FileInputStream("input_vector.txt");
            fstream = new FileInputStream(inputFile.getName());
            in = new DataInputStream(fstream);
            br = new BufferedReader(new InputStreamReader(in));
            /*
             * Synchronize threads.
             */
            try {
                InitClients.barrier.await();
            }
            catch (Exception e) {
                System.out.println(e);
            }
          
            /*
             * The main loop. Continues until the end of file 
             * is found.
             */
            while (!eof) {
                /*
                 * Read a line and check that it is not null.
                 */
                if ((strLine = br.readLine()) != null) {
                    /*
                     * Figure out the action to be taken.
                     */
                    int result = parseAction(strLine);
                    /*
                     * If the return value was above zero, this means 
                     * that this client is supposed to send a message to 
                     * another client.
                     */
                    if (result > 0) {
                        try {
                            /*
                             * Synchronize with the receiver.
                             */
                            InitClients.msgBarrier.await();
                            /*
                             * Open a socket and set client I/O to 
                             * blocking. Connect to the given address.
                             */
                            SocketChannel client = SocketChannel.open();
                            client.configureBlocking(true);
                            //client.connect(new InetSocketAddress("localhost", parsePort(result)));
                            client.connect(new InetSocketAddress(this.parseHost(result), parsePort(result)));
                            ObjectOutputStream oos = new ObjectOutputStream(client.socket().getOutputStream());
                            /*
                             * Increment own clock.
                             */
                            this.increment();
                            /*
                             * Send the timestamp vector to the 
                             * destination.
                             */
                            oos.writeObject(this.vectorClock);
                            oos.close();
                        }
                        catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    /*
                     * If the return value was zero, this means that 
                     * this client is supposed to receive a message. 
                     */
                    else if (result == 0) {
                        try {
                            /*
                             * Synchronize with the sender.
                             */
                            InitClients.msgBarrier.await();
                            /*
                             * Check for incoming requests.
                             */
                            sc = ssc.accept();
                            /*
                             * If there was a request...
                             */
                            if (sc != null) {
                                /*
                                 * Read the vector from the socket and 
                                 * compare it to this client's vector and 
                                 * synchronize them.
                                 */
                                ObjectInputStream ois = new ObjectInputStream(sc.socket().getInputStream());
                                int[] receivedVector = (int[])ois.readObject();
                                synchronize(receivedVector);
                                /*
                                 * Increment own vector.
                                 */
                                this.increment();
                                sc.close();
                            }
                        }
                        catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                    /*
                     * Synchronize each thread at the end of an iteration.
                     */
                    try {
                        InitClients.barrier.await();
                    }
                    catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                /*
                 * If the end of file was reached..
                 */
                else {
                    /*
                     * ..synchronize clients..
                     */
                    try {
                        InitClients.barrier.await();
                    }
                    catch (Exception e) {
                        e.printStackTrace();
                    }
                    /*
                     * ..and set the EOF flag to true.
                     */
                    eof = true;
                }
            }
           
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        try {
            InitClients.barrier.await();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        /*
         * A CountDownLatch for turn taking when printing out the 
         * resulting vectors.
         */
        try {
            while (true) {
                /*
                 * If the value of the CountDownLatch equals the client number 
                 * of this client, proceed and print out vector and 
                 * afterwards reduce the CountDownLatch by one.
                 */
                if (InitClients.printCd.getCount() == (this.clientNumber + 1)) {
                    this.printVector();
                    InitClients.printCd.countDown();
                    break;
                }
            }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /*
     * Method for printing out the timestamp vector.
     */
    private void printVector() {
        System.out.print("{Client[" + (clientNumber + 1) + "]}{");
        for (int i = 0; i < vectorClock.length; i++) {
            System.out.print(vectorClock[i] + "\t");
        }
        System.out.println("}");
    }
}
